/**
 * Description:     读取或者写入一个bmp文件。
 * Attention:       使用本库进行bmp文件的读取或者写入，bmp文件的位深必须为8的倍数，否则将造成不可预测的行为。
 *                  使用本库后请及时释放指针！！ 
 *                  本头文件在数据传输的时候使用了专用结构体
 *                      typedef struct
 *                      {
 *                          unsigned char *garyscale;
 *                          unsigned int dataCount;
 *                          int direction;
 *                          int biWidth;
 *                          int biHeight;  
 *                       } GARYSCALEBIT;
 * Function:        int getValidity(char *fileName);                        检查文件合法性
 *                  BITMAPFILEHEADER getFileHeader(char *fileName);         获取文件头
 *                  BITMAPINFOHEADER getINFOHeader(char *fileName);         获取信息头
 *                  GARYSCALEBIT getGrayscale(char *fileName);              获取灰度信息
 *                  int bmpWrite(unsigned char *writeIn, char *fileIn, char *fileOut);  
 *                      将fileIn的灰度数据修改为writeIn输入，并以fileOut的名称保存
 * Solution:        如果你在使用本头文件时发现了任何问题，请报告issue.
 * Contributer:     叶月枫
 */

#ifndef bmp_h
#define bmp_h
#include <stdio.h>
#include <stdlib.h>

// 文件头
typedef struct
{
    unsigned short bfType;   // @图片种类，BMP图片固定为BM，表示为十六进制就是0x4d42
    unsigned int bfSize;     // 该图片文件的大小
    unsigned int bfReserved; // 保留字
    unsigned int bfOffBits;  // @实际图片数据的偏移量，即`DIB`的偏移量，也即前三个结构体的大小
} BITMAPFILEHEADER;

// 信息头
typedef struct
{
    unsigned int biSize;         // 指定此结构体的长度
    int biWidth;                 // @bmp图片的宽度
    int biHeight;                // @bmp图片的高度。为负数的时候，从上到下排列；为正数的时候反之
    unsigned short biPlanes;     // 平面数，显示器只有一个平面，所以一般为1
    unsigned short biBitCount;   // 颜色位数，目前一般用24位或32位
    unsigned int biCompression;  // 压缩方式，可以是0，1，2，0表示不压缩，BMP为不压缩，所以为0
    unsigned int biSizeImage;    // 实际位图数据占用的字节数.由于上面不压缩，所以这里填0即可
    int biXPelsPerMeter;         // X方向分辨率，即每米有多少个像素，可以省略
    int biYPelsPerMeter;         // Y方向分辨率，即每米有多少个像素，可以省略
    unsigned int biClrUsed;      // 使用的颜色数，如果为0，则表示默认值(2^颜色位数)
    unsigned int biClrImportant; // 重要颜色数，如果为0，则表示所有颜色都是重要的
} BITMAPINFOHEADER;

// 通用结构体
#ifndef grayscalebit
#define grayscalebit
typedef struct
{
    unsigned char *garyscale;
    unsigned int dataCount;
    int direction;
    int biWidth;                 // @bmp图片的宽度
    int biHeight;                // @bmp图片的高度。为负数的时候，从上到下排列；为正数的时候反之
} GARYSCALEBIT;
#endif

// 声明函数
int getValidity(char *fileName);
BITMAPFILEHEADER getFileHeader(char *fileName);
BITMAPINFOHEADER getInfoHeader(char *fileName);
GARYSCALEBIT getGrayscale(char *fileName);
int bmpWrite(unsigned char *writeIn, char *fileIn, char *fileOut);

/**
 * Function:    获取bmp文件的合法性
 * Parameter:   fileName    [in]文件名
 * Return:      1           合法
 *              0           不合法
 *              -1          文件打开失败
 */
int getValidity(char *fileName)
{
    // 打开文件
    FILE *fp = fopen(fileName, "rb");

    // 如果文件打开失败，返回-1
    if (!fp)
    {
        return -1;
    }

    // 定义一个unsigned char类型的数组，用于存储bmp文件头
    unsigned char bmpHeader[2];

    // 从文件中读取2个字节的数据，存储到bmpHeader数组中
    fread(bmpHeader, sizeof(unsigned char), 2, fp);

    // 关闭文件
    fclose(fp);

    // 判断bmpHeader数组中的前两个字节是否为'B'和'M'
    if (bmpHeader[0] == 'B' && bmpHeader[1] == 'M')
    {
    // 如果是，返回1
        return 1;
    }
    else
    {
    // 否则，返回0
        return 0;
    }
}

/**
 * Function:    读取bmp文件头
 * Parameter:   fileName            [in]文件名
 * Return:      BITMAPFILEHEADER
 */
BITMAPFILEHEADER getFileHeader(char *fileName)
{
    // 打开文件
    FILE *fp = fopen(fileName, "rb");

    // 处理文件打开失败的情况
    if (fp == NULL)
    { 
        printf("Error opening file %s\n", fileName);
        exit(EXIT_FAILURE);
    }

    // 新建文件头
    BITMAPFILEHEADER fileHeader;

    // 读取文件头
    // 图片种类
    if (fread(&fileHeader.bfType, sizeof(unsigned short), 1, fp) != 1)
    {
        fclose(fp);
        printf("Error reading file header from file %s\n", fileName);
        exit(EXIT_FAILURE);
    }
    // 该图片文件的大小
    if (fread(&fileHeader.bfSize, sizeof(unsigned int), 1, fp) != 1)
    {
        fclose(fp);
        printf("Error reading file header from file %s\n", fileName);
        exit(EXIT_FAILURE);
    }
    // 保留字
    if (fread(&fileHeader.bfReserved, sizeof(unsigned int), 1, fp) != 1)
    {
        fclose(fp);
        printf("Error reading file header from file %s\n", fileName);
        exit(EXIT_FAILURE);
    }
    // 实际图片数据的偏移量
    if (fread(&fileHeader.bfOffBits, sizeof(unsigned int), 1, fp) != 1)
    {
        fclose(fp);
        printf("Error reading file header from file %s\n", fileName);
        exit(EXIT_FAILURE);
    }

    // 关闭文件
    fclose(fp);

    // 返回数值
    return fileHeader;
}

/**
 * Function:    读取bmp信息头
 * Parameter:   fileName            [in]文件名
 * Return:      BITMAPINFOHEADER
 */
BITMAPINFOHEADER getInfoHeader(char *fileName)
{
    // 打开文件
    FILE *fp = fopen(fileName, "rb");

    // 处理文件打开失败的情况
    if (fp == NULL)
    { 
        printf("Error opening file %s\n", fileName);
        exit(EXIT_FAILURE);
    }

    // 新建信息头
    BITMAPINFOHEADER infoHeader;

    // 跳过文件头到信息头的位置
    if (fseek(fp, 14L, SEEK_SET) != 0)
    {
        fclose(fp);
        printf("Error seeking in file %s\n", fileName);
        exit(EXIT_FAILURE);
    }

    // 读取信息头
    // 此结构体的长度
    if (fread(&infoHeader.biSize, sizeof(unsigned int), 1, fp) != 1)
    {
        fclose(fp);
        printf("Error reading info header from file %s\n", fileName);
        exit(EXIT_FAILURE);
    }
    // bmp图片的宽度
    if (fread(&infoHeader.biWidth, sizeof(int), 1, fp) != 1)
    {
        fclose(fp);
        printf("Error reading info header from file %s\n", fileName);
        exit(EXIT_FAILURE);
    }
    // bmp图片的高度
    if (fread(&infoHeader.biHeight, sizeof(int), 1, fp) != 1)
    {
        fclose(fp);
        printf("Error reading info header from file %s\n", fileName);
        exit(EXIT_FAILURE);
    }
    // 平面数
    if (fread(&infoHeader.biPlanes, sizeof(unsigned short), 1, fp) != 1)
    {
        fclose(fp);
        printf("Error reading info header from file %s\n", fileName);
        exit(EXIT_FAILURE);
    }
    // 颜色位数
    if (fread(&infoHeader.biBitCount, sizeof(unsigned short), 1, fp) != 1)
    {
        fclose(fp);
        printf("Error reading info header from file %s\n", fileName);
        exit(EXIT_FAILURE);
    }
    // 压缩方式
    if (fread(&infoHeader.biCompression, sizeof(unsigned int), 1, fp) != 1)
    {
        fclose(fp);
        printf("Error reading info header from file %s\n", fileName);
        exit(EXIT_FAILURE);
    }
    // 实际位图数据占用的字节数
    if (fread(&infoHeader.biSizeImage, sizeof(unsigned int), 1, fp) != 1)
    {
        fclose(fp);
        printf("Error reading info header from file %s\n", fileName);
        exit(EXIT_FAILURE);
    }
    // X方向分辨率
    if (fread(&infoHeader.biXPelsPerMeter, sizeof(int), 1, fp) != 1)
    {
        fclose(fp);
        printf("Error reading info header from file %s\n", fileName);
        exit(EXIT_FAILURE);
    }
    // Y方向分辨率
    if (fread(&infoHeader.biYPelsPerMeter, sizeof(int), 1, fp) != 1)
    {
        fclose(fp);
        printf("Error reading info header from file %s\n", fileName);
        exit(EXIT_FAILURE);
    }
    // 使用的颜色数
    if (fread(&infoHeader.biClrUsed, sizeof(unsigned int), 1, fp) != 1)
    {
        fclose(fp);
        printf("Error reading info header from file %s\n", fileName);
        exit(EXIT_FAILURE);
    }
    // 重要颜色数
    if (fread(&infoHeader.biClrImportant, sizeof(unsigned int), 1, fp) != 1)
    {
        fclose(fp);
        printf("Error reading info header from file %s\n", fileName);
        exit(EXIT_FAILURE);
    }

    // 关闭文件
    fclose(fp);

    // 返回信息头
    return infoHeader;
}

/**
 * Function:    读取灰度数据
 * Parameter:   fileName            [in]文件名
 * Return:      GARYSCALEBIT        [out]
 *
 * GARYSCALEBIT.garyscale           第一个像素的指针
 * GARYSCALEBIT.dataCount           像素数
 */
GARYSCALEBIT getGrayscale(char *fileName)
{
    // 打开文件
    FILE *fp = fopen(fileName, "rb");

    // 处理文件打开失败的情况
    if (fp == NULL)
    {
        printf("Error opening file %s\n", fileName);
        exit(EXIT_FAILURE);
    }

    // 跳过文件头和信息头
    if (fseek(fp, getFileHeader(fileName).bfOffBits, SEEK_SET) != 0)
    {
        fclose(fp);
        printf("Error seeking in file %s\n", fileName);
        exit(EXIT_FAILURE);
    }

    // 总的数据量
    unsigned int dataCount = getInfoHeader(fileName).biHeight * getInfoHeader(fileName).biWidth;
    // 图像的位深
    unsigned short bitCount = getInfoHeader(fileName).biBitCount;
    // 分配内存空间
    unsigned char *grayscale = (unsigned char *)calloc(dataCount, sizeof(unsigned char));

    // 内存分配失败
    if (grayscale == NULL)
    {
        fclose(fp);
        printf("Memory allocation failed\n");
        exit(EXIT_FAILURE);
    }

    // 遍历所有的灰度值
    int k = 0;
    for (int i = 0; i < getInfoHeader(fileName).biHeight; i++)
    {
        for (int j = 0; j < getInfoHeader(fileName).biWidth; j++)
        {
            fread(&grayscale[k * (bitCount / 8)], bitCount / 8, 1, fp);
            k++;
        }   

        // 跳过补位符
        if (getInfoHeader(fileName).biWidth % 4 != 0)
        {
            if (fseek(fp, 4 - getInfoHeader(fileName).biWidth % 4, SEEK_CUR) != 0)
            {
                fclose(fp);
                printf("Error seeking in file %s\n", fileName);
                exit(EXIT_FAILURE);
            }
        }
    }

    int biWidth = getInfoHeader(fileName).biWidth;
    int biHeight = getInfoHeader(fileName).biHeight;
    
    // 输出位图的两种排列方式
    // 此处在本图像处理程序中不重要
    if (getInfoHeader(fileName).biHeight < 0)
    {
        GARYSCALEBIT temp = {&grayscale[0], dataCount, 1, biWidth, biHeight};
        fclose(fp);
        return temp;
    }
    else
    {
        GARYSCALEBIT temp = {&grayscale[0], dataCount, -1, biWidth, biHeight};
        fclose(fp);
        return temp;
    }
}

/**
 * Function:    写入灰度数据
 * Parameter:   writIn              [in]指向灰度数据的指针
 *              fileIn              [in]原文件名
 *              fileOut             [in]新文件名
 * Return:      int                 [out]写入成功返回0
 */
int bmpWrite(unsigned char *writeIn, char *fileIn, char *fileOut)
{
    // 文件打开
    FILE *origin = fopen(fileIn, "rb"), *destination = fopen(fileOut, "wb");
    unsigned char buffer[1024]; // 缓冲区
    size_t bytes; // 写入数据量
    int bfHeadSize = getFileHeader(fileIn).bfOffBits; // 获取头文件长度
    int count = 0; // 文件头复制计数器

    // 处理原文件打开失败的情况
    if (origin == NULL)
    { 
        printf("Error opening file %s\n", fileIn);
        exit(EXIT_FAILURE);
    }

    // 处理新文件创建失败的情况
    if (destination == NULL)
    { 
        printf("Error touching file %s\n", fileOut);
        fclose(origin);
        exit(EXIT_FAILURE);
    }

    // 复制文件
    while ((bytes = fread(buffer, 1, sizeof(buffer), origin)) > 0 && count < bfHeadSize) {
        fwrite(buffer, 1, bytes, destination);
        count ++;
    }

    // 检查是否发生错误
    if (ferror(origin) || ferror(destination)) {
        printf("Error reading or writing files");
        fclose(origin);
        fclose(destination);
        exit(EXIT_FAILURE);
    }

    // 跳过文件的头
    if (fseek(destination, getFileHeader(fileIn).bfOffBits, SEEK_SET) != 0)
    {
        fclose(destination);
        printf("Error seeking in file %s\n", fileIn);
        exit(EXIT_FAILURE);
    }

    // 数据写入
    unsigned short bitCount = getInfoHeader(fileIn).biBitCount;
    int k = 0;
    for (int i = 0; i < getInfoHeader(fileIn).biHeight; i++)
    {
        for (int j = 0; j < getInfoHeader(fileIn).biWidth; j++)
        {
            fwrite(&writeIn[k * (bitCount / 8)], bitCount / 8, 1, destination);
            k++;
        }   

        // 跳过补位符
        if (getInfoHeader(fileIn).biWidth % 4 != 0)
        {
            if (fseek(destination, 4 - getInfoHeader(fileIn).biWidth % 4, SEEK_CUR) != 0)
            {
                fclose(destination);
                printf("Error seeking in file %s\n", fileIn);
                exit(EXIT_FAILURE);
            }
        }
    }

    // 关闭文件
    fclose(destination);
    fclose(origin);
    return 0;
}

#endif